package com.acme.javaschool.intense.sems.sem5memory;

import java.nio.ByteBuffer;

public class ByteBufferHashMap {
    private final ByteBuffer data;
    private int totalCapacity;
    private int nonCollisionCapacity;
    private int entrySize;
    private int lastFreeIndex;

    public ByteBufferHashMap(ByteBuffer data) {
        this.data = data;

        entrySize = 12; //too much, indeed
        totalCapacity = (data.capacity() - 2) / entrySize;
        nonCollisionCapacity = totalCapacity / 2;
        lastFreeIndex = totalCapacity - 1;

        for (int i = 0; i < totalCapacity; i++) {
            this.data.putInt(0);
            this.data.putInt(0);
            this.data.putInt(-1);
        }
    }

    public void showBuffer() {
        data.rewind();
        for (int i = 0; i < totalCapacity; i++) {
            if (i == nonCollisionCapacity) {
                System.out.print("||");
            }
            System.out.print(data.getInt() + " ");
            System.out.print(data.getInt() + " ");
            System.out.print(data.getInt() + ";\t");
        }
        System.out.println();
    }

    public int put(int key, int value) {
        int returnValue = value;

        int startIndex = getIndexForKey(key);
        int oldRef = getRef(startIndex);
        int prevIndex = startIndex;
        int curIndex = startIndex;

        while (true) {
            if (oldRef == -1) {
                break;
            } else if (oldRef == 0) {
                if (key == getKey(curIndex)) {
                    returnValue = getVal(curIndex);
                    break;
                }

                if (lastFreeIndex < nonCollisionCapacity) {
                    throw new RuntimeException("Too many collisions.");
                }
                curIndex = lastFreeIndex;
                lastFreeIndex--;
                break;
            } else {
                if (key == getKey(curIndex)) {
                    returnValue = getVal(curIndex);
                    break;
                }
                prevIndex = curIndex;
                curIndex = oldRef;
                oldRef = getRef(prevIndex);
            }
        }

        putToBuffer(curIndex, key, value, 0);
        if (prevIndex != curIndex) {
            setRef(prevIndex, curIndex);
        }
        return returnValue;
    }

    public Integer get(int key) {

        int index = getIndexForKey(key);
        while (true) {
            int oldRef = getRef(index);

            if (oldRef == -1) {
                return null;
            } else if (oldRef == 0) {
                if (getKey(index) == key) {
                    return getVal(index);
                } else {
                    index = lastFreeIndex;
                }
            } else {
                if (getKey(index) == key) {
                    if (oldRef == -2) {
                        return null;
                    }
                    return getVal(index);
                } else {
                    if (oldRef == -2) {
                        index = getVal(index);
                    } else {
                        index = oldRef;
                    }
                }
            }
        }

    }

    public Integer remove(int key) {
        int index = getIndexForKey(key);
        int returnValue = getVal(index);
        int oldRef = getRef(index);
        int prevIndex = index;

        while (true) {
            if (oldRef == -1) {
                return null;
            } else {
                if (getKey(index) == key) {
                    if (oldRef == 0) {
                        returnValue = getVal(index);
                        setRef(index, -1);
                        break;
                    } else {
                        returnValue = getVal(index);
                        fillGap(index);
                        break;
                    }
                } else {
                    if (oldRef == 0) {
                        return null;
                    } else {
                        prevIndex = index;
                        index = oldRef;
                        oldRef = getRef(prevIndex);
                    }
                }
            }
        }

        updateLastFreeIndex(index);

        return returnValue;
    }

    private void fillGap(int startIndex) {
        int oldRef = getRef(startIndex);
        int index = startIndex;
        int prevIndex = startIndex;
        int endOfChainIndex = startIndex;

        while (true) {
            if (oldRef == -1) {
                endOfChainIndex = prevIndex;
                break;
            } else if (oldRef == 0) {
                endOfChainIndex = index;
                break;
            } else {
                prevIndex = index;
                index = getRef(index);
                oldRef = getRef(index);
            }
        }

        putToBuffer(startIndex, getKey(endOfChainIndex), getVal(endOfChainIndex), getRef(startIndex));
        setRef(endOfChainIndex, -1);
    }

    private void updateLastFreeIndex(int index) {
        if (index >= nonCollisionCapacity && lastFreeIndex < index) {
            lastFreeIndex = index;
        }
    }




    public int capacity() {
        return nonCollisionCapacity;
    }

    private int getKey(int index) {
        return data.getInt(index * entrySize);
    }

    private int getVal(int index) {
        return data.getInt(index * entrySize + 1 * 4);
    }

    private void setVal(int index, int val) {
        data.putInt(index * entrySize + 1 * 4, val);
    }

    private int getRef(int index) {
        return data.getInt(index * entrySize + 2 * 4);
    }

    private void setRef(int index, int ref) {
        data.putInt(index * entrySize + 2 * 4, ref);
    }

    private void putToBuffer(int index, int key, int value, int ref) {
        putToBuffer(index, key, value);
        setRef(index, ref);
    }

    private void putToBuffer(int index, int key, int value) {
        index *= entrySize;
        data.putInt(index, key);
        data.putInt(index + 1 * 4, value);
        data.putInt(index + 2 * 4, 0);
    }

    private int standardHash(int k) {
        int h = 0;
        h ^= k;

        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    private int hash(int k) {
        return standardHash(k);
    }

    private int indexForHash(int hash) {
        return hash % nonCollisionCapacity;
    }

    private int getIndexForKey(int key) {
        return indexForHash(hash(key));
    }

}
